package com.zyc.util;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.zyc.pojo.Table;
import com.zyc.pojo.TableProperty;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.*;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * HappyUtil
 * 里面有一些可能会用到的方法
 * 是提供给所有类的底层工具类
 */
public class HappyUtil {
    /**
     * 用来读取数据库的连接池
     */
    private static DataSource dataSource = null;

    /**
     * 里面存储了所有properties文件的所有属性
     */
    public static Properties properties = new Properties();

    /**
     * 数据库对象
     */
    private static List<Table> tableList;

    /**
     * 获取tableList列表
     *
     * @return tableList列表
     */
    public static List<Table> getTableList() {
        return tableList;
    }

    /**
     * 它能保证同一个线程下保存的对象是同一个
     */
    private static final ThreadLocal<Connection> tl;

    //读取properties配置文件，并且配置数据库连接池，初始化tableList列表
    static {
        // 因为静态代码块不能放在tl之前运行，这样的话tl不能初始化。
        // 所以放在这里new对象，否则如果掉换静态代码块和tl代码的位置就会报错
        // 那就有点黑魔法的那个味道了。
        tl = new ThreadLocal<>();
        InputStream in = HappyUtil.class.getClassLoader().getResourceAsStream("happy.properties");
        try {
            properties.load(in);
            //创建连接池
            dataSource = DruidDataSourceFactory.createDataSource(properties);
            //获得连接
            Connection connection = getConnection();
            //获取数据库的数据
            DatabaseMetaData db = connection.getMetaData();
            //获得数据库中的所有表
            ResultSet wxb = db.getTables(null, null, null, new String[]{"TABLE"});
            //创建表的列表
            List<Table> tables = new LinkedList<>();
            //遍历数据库中的所有表
            while (wxb.next()) {
                //获得表名
                String tableName = wxb.getString(3);
                //根据表名，查到表中的主键名
                PreparedStatement ps = connection.prepareStatement("show full columns from " + "`" + tableName + "`");
                ResultSet primaryKeys = connection.getMetaData().getPrimaryKeys(null, null, tableName);
                String primaryKey = null;
                if (primaryKeys.next()) {
                    primaryKey = primaryKeys.getString("COLUMN_NAME");
                }

                //创建表中属性的列表
                List<TableProperty> tableProperties = new LinkedList<>();
                //声明变量，记录主键。
                TableProperty primaryKeyProperty = null;
                //遍历表中的所有的属性
                ResultSet tableObj = ps.executeQuery();
                while (tableObj.next()) {
                    //获得属性的注释
                    String comment = tableObj.getString("Comment");
                    if (comment == null) {
                        comment = "";
                    }
                    //获得属性名
                    String field = tableObj.getString("Field");
                    //获得属性的类型
                    String type = tableObj.getString("Type");
                    //判断是否是主键
                    boolean isPrimary = primaryKey != null && primaryKey.equals(field);
                    //创建表中属性的对象
                    TableProperty tableProperty = new TableProperty(field, pascalToUpperCamel(field), pascalToLowerCamel(field), type, toJavaType(type), comment, isPrimary);
                    //如果是主键，则将该对象记录下来
                    if (isPrimary) {
                        primaryKeyProperty = tableProperty;
                    }
                    //把创建的对象加入列表
                    tableProperties.add(tableProperty);
                }
                //根据获得的属性列表，创建一个表
                Table table = new Table(tableName, pascalToUpperCamel(tableName), pascalToLowerCamel(tableName), primaryKeyProperty, tableProperties);
                //将表加入列表中
                tables.add(table);
            }
            //给TABLE_LIST赋值，完毕。
            tableList = tables;
            closeAll(wxb);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取数据库连接
     */
    private static Connection getConnection() {
        Connection conn = tl.get();
        if (conn == null) {
            try {
                //从连接池中获得连接
                conn = dataSource.getConnection();
                //存入ThreadLocal
                tl.set(conn);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return conn;
    }

    /**
     * 释放所有资源
     *
     * @param autoCloseables 所有可以关闭的资源，如果资源是tl中的资源
     *                       则会清空tl
     */
    public static void closeAll(AutoCloseable... autoCloseables) {
        for (AutoCloseable ac : autoCloseables) {
            if (ac != null) {
                try {
                    //判断如果连接是Connection类的
                    if (ac instanceof Connection) {
                        //从Threadlocal中移除
                        tl.remove();
                    }
                    ac.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 驼峰命名法转为帕斯卡命名法
     *
     * @param camelName 驼峰命名法的名字
     * @return 帕斯卡命名法的名字
     */
    public static String camelToPascal(String camelName) {
        Pattern pattern = Pattern.compile("([A-Z])");
        Matcher matcher = pattern.matcher(camelName);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            matcher.appendReplacement(sb, "_" + matcher.group(1).toLowerCase());
        }
        matcher.appendTail(sb);
        String pascalName = sb.toString();
        if (pascalName.startsWith("_")) {
            pascalName = pascalName.substring(1);
        }
        return pascalName;
    }

    /**
     * 帕斯卡命名法转为小驼峰命名法
     *
     * @param pascalName 帕斯卡命名法的名字
     * @return 小驼峰命名法的名字
     */
    public static String pascalToLowerCamel(String pascalName) {
        pascalName = pascalName.toLowerCase();
        Pattern pattern = Pattern.compile("_(\\w)", Pattern.MULTILINE | Pattern.DOTALL);
        Matcher matcher = pattern.matcher(pascalName);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
        }
        matcher.appendTail(sb);
        return sb.toString();
    }

    /**
     * 帕斯卡命名法转为大驼峰命名法
     *
     * @param pascalName 帕斯卡命名法的名字
     * @return 大驼峰命名法的名字
     */
    public static String pascalToUpperCamel(String pascalName) {
        String lowerCamel = pascalToLowerCamel(pascalName);
        return String.valueOf(lowerCamel.charAt(0)).toUpperCase() + lowerCamel.substring(1);
    }

    /**
     * 将数据库中的类型字符串转化为相应的java类型字符串
     *
     * @param sqlType sql类型数据字符串
     * @return java类型数据字符串
     */
    public static String toJavaType(String sqlType) {
        sqlType = sqlType.replaceAll("\\(.+\\)", "");
        if (sqlType.equalsIgnoreCase("varchar") || sqlType.equalsIgnoreCase("text")
                || sqlType.equalsIgnoreCase("char") || sqlType.equalsIgnoreCase("longtext")) {
            return "String";
        } else if (sqlType.equalsIgnoreCase("smallint") || sqlType.equalsIgnoreCase("tinyint")
                || sqlType.equalsIgnoreCase("int") || sqlType.equalsIgnoreCase("int unsigned")
                || sqlType.equalsIgnoreCase("Integer")
                || sqlType.equalsIgnoreCase("enum") || sqlType.equalsIgnoreCase("smallint unsigned")) {
            return "Integer";
        } else if (sqlType.equalsIgnoreCase("bigInt")) {
            return "Long";
        }else if (sqlType.equalsIgnoreCase("date") || sqlType.equalsIgnoreCase("time")
                || sqlType.equalsIgnoreCase("datetime") || sqlType.equalsIgnoreCase("timestamp")) {
            return "Date";
        } else if (sqlType.equalsIgnoreCase("double") || sqlType.equalsIgnoreCase("float")) {
            return "Double";
        } else if (sqlType.equalsIgnoreCase("decimal")) {
            return "BigDecimal";
        }
        return null;
    }
}
